/**
 * 封装一个对象迭代的方法
 *  基于传统观念的 for in 循环会存在一些弊端「比如性能比较差、即可迭代私有的也可迭代共有的，只能迭代“可枚举、非Symbol 类型的属性。。。」
 *  + 解决思路： 获取对象所有的私有属性「私有的、不论是否可枚举、不论类型」
 *      Object.getOwnPropertyNames(obj) -> 获取对象 非 Symbol 类型的私有属性「无关是否可枚举」
 *      Object.getOwnPropertySymbols(ojb) -> 获取对象 Symbol 类似的属性
 *      获取所有的私有属性
 *      const allKeys =  Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(obj))
 *      const allKeys = Reflect.ownKeys(arr); // ES 获取所有的私有属性，不兼容 IE
 */
const each = function each(obj, callback) {
  if (!obj || typeof obj !== "object") {
    throw new TypeError("obj is not a object");
  }

  if (typeof callback !== "function") {
    throw new TypeError("callback is not a function");
  }

  const allOwnKeys = Reflect.ownKeys(obj);
  allOwnKeys.forEach((key) => {
    callback(obj[key], key);
  });
};
/**
 * createElement 创建虚拟 Dom 对象
 */
export function createElement(ele, props, ...children) {
  const virtualDOM = {
    $$typeof: Symbol("react.element"),
    key: ele,
    ref: null,
    type: ele,
    // 元素的相关属性，children 是子节点信息
    props: props || {},
  };

  const len = children.length;

  if (len === 1) virtualDOM.props.children = children[0];

  if (len > 1) virtualDOM.props.children = children;

  return virtualDOM;
}

/**
 * render： 把虚拟 DOM 变成真实 DOM
 * V16
 *  ReactDOM.render(<>...</>, document.getElementById("root"))
 *
 * V18
 *  const root = ReactDOM.createRoot(document.getElementById("root"));
 *  root.render(<>...</>)
 */

export function render(virtualDOM, container) {
  const { type, props } = virtualDOM;

  // 处理 virtualDOM 的 type 是 string （原生标签的形式）
  if (typeof type === "string") {
    // 存储的是标签名字, 动态创建这个标签
    const ele = document.createElement(type);

    each(props, (value, key) => {
      // className 的处理： value 存储的是样式类名
      if (key === "className") {
        // ele.setAttribute("class", value) // 也是可以的
        ele.className = value; // ele.key 只有内置的属性才能加到 dom 标签上
        return;
      }

      // style 的粗粝： value 是对象，需要特殊处理
      if (key === "style") {
        each(value, (val, attr) => {
          ele.style[attr] = val;
        });
        return;
      }

      // children 子节点处理, value 存储的 children 属性值
      if (key === "children") {
        let children = value;
        // 将 children 只有一个的时候处理为 数组
        if (!Array.isArray(children)) children = [children];

        children.forEach((child) => {
          // 子节点是文本节点： 直接添加到 ele 上
          if (/^(number|string)$/.test(typeof child)) {
            // 创建文本节点并将 文本节点添加到 ele 中
            ele.appendChild(document.createTextNode(child));
            return;
          }

          // 子节点(child)如果是 virtualDOM, 递归处理
          render(child, ele);
        });
        return;
      }
      // 将 key value 设置元素的标签上设置键值对
      ele.setAttribute(key, value);
    });
    // 创建的标签添加到容器当中
    container.appendChild(ele);
  }

  // TODO: function component handle
  // TODO: class component handle
}

/**
 * 其他知识：
 * 一般来讲，内置属性都是不可枚举的「枚举： 可以别列举、例如 被 for/in Object.keys等列举出来」
 * 可以设置成员的枚举性 Object.defineProperty();
 *
 * const dom = document.getElementById(id);
 * 为元素设置属性「自定义/内置」两种方式
 * 1. dom[key] = value or dom.key = value
 * 对于内置属性，是设置在元素的标签上；对于自定义属性来讲，是给对象的堆内存空间中新增成员（但是不会设置到标签上）
 *  获取元素： dom[key] or dom.key
 *  删除： delete dom.key （就像对象一样操作即可）
 *
 * 2. dom.setAttribute(key,value)
 * 直接在元素的标签上设置键值对
 *  获取 key 的值： dom.getAttribute(key)
 *  删除 key： dom.removeAttribute(key)
 */
